package com.huawei.bitfun.insight.utils;

import com.google.common.reflect.ClassPath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class InsightExClassPathUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(InsightExClassPathUtil.class);

    private static final String JAR_URL_SEPARATOR = "!/";

    private static final String ROOT_PACKAGE = "com.huawei.bitfun.insight";

    private static final String ROOT_RESOURCE = "com/huawei/bitfun/insight";

    public static List<Class<?>> findClassWithAnyAnnotation(Collection<Class<? extends Annotation>> annotations) {
        List<Class<?>> projectClasses = findProjectClasses();
        return projectClasses.stream()
                .filter((clazz) -> annotations.stream().anyMatch(clazz::isAnnotationPresent))
                .collect(Collectors.toList());
    }

    private static List<Class<?>> findProjectClasses() {
        URL rootUrl = InsightExClassPathUtil.class.getClassLoader().getResource(ROOT_RESOURCE);
        if (rootUrl == null) {
            LOGGER.warn("Not found url for: {}.", ROOT_RESOURCE);
            return Collections.emptyList();
        }
        URL scanUrl = rootUrl;
        String classUrlPath = rootUrl.getPath();
        int jarUrlIndex = classUrlPath.lastIndexOf(JAR_URL_SEPARATOR);
        Set<ClassPath.ClassInfo> classInfoList;
        try {
            if (jarUrlIndex >= 0) {
                scanUrl = URI.create(classUrlPath.substring(0, jarUrlIndex)).toURL();
            }
            URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{scanUrl}, Thread.currentThread().getContextClassLoader());
            classInfoList = ClassPath.from(classLoader).getTopLevelClassesRecursive(ROOT_PACKAGE);
        } catch (IOException e) {
            LOGGER.warn("Failed to search project classes.", e);
            return Collections.emptyList();
        }


        ArrayList<Class<?>> projectClassList = new ArrayList<>();
        for (ClassPath.ClassInfo classInfo : classInfoList) {
            try {
                projectClassList.add(Class.forName(classInfo.getName()));
            } catch (ClassNotFoundException | NoClassDefFoundError var13) {
                LOGGER.warn("No class: {}.", classInfo.getName());
            }
        }
        return projectClassList;
    }
}
